home *** CD-ROM | disk | FTP | other *** search
- /* display a tear-off menu in a windoid
- 94/01/09 aih removed kMenuMargin constant
- 93/12/01 aih created */
-
- #include "AsmLib.h"
- #include "DrawLib.h"
- #include "FloatMenuLib.h"
- #include "LowMemLib.h"
- #include "MathLib.h"
- #include "MacLib.h"
- #include "MemoryLib.h"
- #include "MenuLib.h"
- #include "RectangleLib.h"
- #include "ResourceConstantsLib.h"
- #include "WindoidWDEF.h"
- #include "WindowLib.h"
-
- /* message sent to mdef when there's a click in a floating menu's window */
- #define mFloatChooseMsg (128)
-
- /* margin around menu in which not to tear off menu */
- #define TEAR_MARGIN (7)
-
- /* glue for calling our menu definition function */
- typedef struct {
- short jmp; /* jump instruction */
- MenuProcPtr proc; /* address of original mdef function */
- FloatMenuHandle fmenu; /* the floating menu */
- } FloatMenuGlueType, *FloatMenuGluePtr, **FloatMenuGlueHandle;
-
- /* list of all floating menus */
- static FloatMenuHandle gFloatMenuList;
-
- static void FloatMenuDrawDirect(FloatMenuHandle fmenu);
- static void FloatMenuDrawHook(OffscreenHandle offscreen, void *data);
-
- /*----------------------------------------------------------------------------*/
- /* calling menu definition functions */
- /*----------------------------------------------------------------------------*/
-
- static void mdef_call(Handle mdef, short msg, MenuHandle menu,
- Rect *bounds, Point hit, short *which)
- {
- SignedByte state = HandleLock(mdef);
- ((MenuProcPtr) *mdef)(msg, menu, bounds, hit, which);
- HandleRestore(mdef, state);
- }
-
- static void mdef(short msg, MenuHandle menu, Rect *bounds,
- Point hit, short *which)
- {
- mdef_call((**menu).menuProc, msg, menu, bounds, hit, which);
- }
-
- static void mdef_draw(MenuHandle menu, Rect *bounds)
- {
- Point hit = { 0, 0 };
- short which = 0;
-
- mdef(mDrawMsg, menu, bounds, hit, &which);
- }
-
- static void mdef_choose(MenuHandle menu, Rect *bounds, Point hit, short *which)
- {
- mdef(mChooseMsg, menu, bounds, hit, which);
- }
-
- static void mdef_size(MenuHandle menu)
- {
- Rect bounds = { 0, 0, 0, 0 };
- Point hit = { 0, 0 };
- short which = 0;
-
- mdef(mSizeMsg, menu, &bounds, hit, &which);
- }
-
- /*----------------------------------------------------------------------------*/
- /* menu definition for a floating window */
- /*----------------------------------------------------------------------------*/
-
- /* drag a gray outline of the menu, return coordinates for torn off menu */
- static Point FloatMenuDrag(FloatMenuHandle fmenu, const Rect *bounds,
- Point startPt)
- {
- Rect content, structure, drag, new, contRect;
- RgnHandle contRgn = NULL;
- Point pos;
-
- require(FloatMenuValid(fmenu));
-
- /* calculate rectangles */
- if ((**fmenu).window) {
- /* we have a window, so get the rectangles from it */
- WinRectangles((**fmenu).window, &structure, &content, &drag, &new);
- }
- else {
- /* No window yet, so calculate the rectangles. This doesn't have
- to be 100% accurate, but it should be within 1 or 2 pixels
- of the real values. */
- content.top = startPt.h;
- content.right = startPt.v;
- content.bottom = content.top + RectHeight(bounds);
- content.right = content.left + RectWidth(bounds);
- structure = content;
- InsetRect(&structure, -kWindoidBorderSize, -kWindoidBorderSize);
- structure.top -= kWindoidDragSize + 1;
- structure.right += kWindoidShadowSize;
- structure.bottom += kWindoidShadowSize;
- drag = content;
- drag.top = content.top - kWindoidDragSize - kWindoidBorderSize;
- drag.bottom = content.top - kWindoidBorderSize;
- }
-
- /* calculate region that excludes the menu bar, this menu, and a
- small margin around the menu; if the mouse leaves contRgn
- (short for "continue region") dragging is stopped */
- contRgn = BeginRgn();
- contRect = *bounds;
- InsetRect(&contRect, -TEAR_MARGIN, -TEAR_MARGIN);
- RectRgn(contRgn, &contRect);
- DiffRgn(GetGrayRgn(), contRgn, contRgn);
-
- /* offset rectangles so their top left
- is next to the cursor */
- pos.h = startPt.h - content.left - 5;
- pos.v = startPt.v - content.top + 5;
- OffsetRect(&structure, pos.h, pos.v);
- OffsetRect(&content, pos.h, pos.v);
- OffsetRect(&drag, pos.h, pos.v);
-
- /* drag an outline of the menu */
- pos = DragRect(&structure, &drag, contRgn, GetGrayRgn(), startPt);
- if (pos.h || pos.v) {
- pos.h += content.left;
- pos.v += content.top;
- }
- EndRgn(contRgn);
- return(pos);
- }
-
- /* task called after a menu has been torn off; positions and shows floating
- menu's window */
- static void FloatMenuTearOffTask(TaskHandle task, void *data)
- {
- FloatMenuHandle fmenu = data;
-
- require(FloatMenuValid(fmenu));
- require(task == (**fmenu).tearTask);
- EventPostTaskDelete(task);
- (**fmenu).tearTask = NULL;
- FloatMenuOpen(fmenu);
- WinMove((**fmenu).window, (**fmenu).position.h, (**fmenu).position.v);
- if (! WinVisible((**fmenu).window)) {
- WinShow((**fmenu).window);
- WinSelect((**fmenu).window);
- }
- }
-
- /* track the mouse and install a task to position the torn off menu's window */
- static void FloatMenuTearOff(FloatMenuHandle fmenu, const Rect *bounds,
- Point pt)
- {
- Point position;
- TaskHandle task = NULL;
-
- require(FloatMenuValid(fmenu));
- GetMouse(&position);
- pt = FloatMenuDrag(fmenu, bounds, pt);
- if (pt.h || pt.v) {
- /* Install a task to be executed after MenuSelect returns.
- If we tried showing or moving the torn-off menu's window
- before MenuSelect returned it would result in weird results
- when MenuSelect tried to restore the saved bits behind the
- menu and they overlapped the window. */
- check(! StillDown());
- check(! (**fmenu).tearTask);
- task = EventPostTaskInsert(FloatMenuTearOffTask, fmenu);
- (**fmenu).tearTask = task;
- (**fmenu).position = pt;
- }
- ensure(FloatMenuValid(fmenu));
- }
-
- /* menu definition function for a floating menu */
- static pascal void FloatMenuMDEF(short msg, MenuHandle menu, Rect *bounds,
- Point hit, short *which)
- {
- FloatMenuHandle fmenu = NULL;
- Boolean tearoff = true;
-
- /* get the float menu from the glue handle */
- fmenu = (**(FloatMenuGlueHandle) (**menu).menuProc).fmenu;
- check(FloatMenuValid(fmenu));
-
- if (msg == mFloatChooseMsg) {
- /* We use the message mFloatChooseMsg to distinguish between
- a click in a torn-off menu and a click in a regular menu
- still attached to the menu bar. The message mFloatChooseMsg
- is only sent when there's a click in a torn-off menu, so that
- we shouldn't try to tear off the menu again. */
- tearoff = false;
- msg = mChooseMsg;
- }
-
- /* call the original menu definition function */
- mdef_call((**fmenu).mdef, msg, menu, bounds, hit, which);
-
- /* Tear off menu if appropriate (we're not already torn off, nothing
- was selected from the menu, the menu's enabled, and there's
- sufficient memory so the menu won't be closed immediately due to
- memory shortage). */
- if (msg == mChooseMsg && tearoff && ! *which &&
- MenuEnabled((**fmenu).menu, 0) &&
- MemWarning() < MEM_WARNING_VERY_LOW)
- {
- FloatMenuTearOff(fmenu, bounds, hit);
- }
- }
-
- /*----------------------------------------------------------------------------*/
- /* floating menu functions */
- /*----------------------------------------------------------------------------*/
-
- /* true if a valid float menu */
- Boolean FloatMenuValid(FloatMenuHandle fmenu)
- {
- check(sizeof(MenuInfo) > sizeof(Str255));
- if (! HandleValidSize(fmenu, sizeof(FloatMenuType))) return(false);
- if (! HandleValidSize((**fmenu).menu, sizeof(MenuInfo) - sizeof(Str255))) return(false);
- return(true);
- }
-
- /* create a new float menu for the specified menu; call only once for any
- one menu */
- FloatMenuHandle FloatMenuBegin(short id)
- {
- volatile FloatMenuHandle fmenu = NULL; /* the floating menu */
- MenuHandle menu = NULL; /* the menu */
- Handle copy; /* for copy of menu data */
- FloatMenuGlueType glue = { ASM_JMP, FloatMenuMDEF, NULL };
-
- require(! FloatMenuFindID(id));
- TRY {
-
- /* create floating menu, add to list of all floating menus */
- fmenu = HandleBeginClear(sizeof(FloatMenuType));
- gFloatMenuList = LLHInsert(gFloatMenuList, fmenu);
-
- /* get menu handle from menu list, or load from resource
- file if not already installed in menu list */
- menu = MenuHandleGet(id);
- if (! menu) {
- menu = GetMenu(id);
- FailNILRes(menu);
- (**fmenu).owner = true;
- }
- HNoPurge((Handle) menu);
- (**fmenu).menu = menu;
-
- /* allocate a handle for the copy of the menu data */
- copy = HandleBegin(0);
- HandlePurge(copy);
- (**fmenu).menuCopy = copy;
-
- /* patch menu def proc so it first calls our function, which allows
- us to track the mouse and install a task to tear off the menu */
- glue.fmenu = fmenu;
- (**fmenu).mdef = (**menu).menuProc;
- HNoPurge((**fmenu).mdef);
- (**menu).menuProc = HandleBegin(sizeof(FloatMenuGlueType));
- BlockMove(&glue, *(**menu).menuProc, sizeof(FloatMenuGlueType));
-
- } CATCH {
- FloatMenuEnd(fmenu);
- } ENDTRY;
- ensure(FloatMenuValid(fmenu));
- return(fmenu);
- }
-
- /* dispose of the floating menu */
- void FloatMenuEnd(FloatMenuHandle fmenu)
- {
- if (fmenu) {
- FloatMenuClose(fmenu);
- gFloatMenuList = LLHDelete(gFloatMenuList, fmenu);
- (**(**fmenu).menu).menuProc = (**fmenu).mdef;
- if ((**fmenu).owner) HandleEnd((**fmenu).menu);
- if ((**fmenu).menuCopy) DisposeHandle((**fmenu).menuCopy);
- EventPostTaskDelete((**fmenu).tearTask);
- HandleEnd(fmenu);
- }
- }
-
- /* create the floating menu's window; has no effect if window already exists */
- void FloatMenuOpen(FloatMenuHandle fmenu)
- {
- OffscreenHandle offscreen = NULL;
- WindowPtr window = NULL;
-
- require(FloatMenuValid(fmenu));
- if (! (**fmenu).window) {
- window = WinGet(RLW_FLOAT_MENU);
- (**fmenu).window = window;
- offscreen = OffscreenBegin(window, FloatMenuDrawHook, fmenu);
- (**fmenu).offscreen = offscreen;
- WinRegister(window, fmenu, FloatMenuEventTable());
- FloatMenuAdjust(fmenu);
- }
- ensure(FloatMenuValid(fmenu));
- }
-
- /* dispose of the floating menu's window but don't dispose of the menu itself;
- no effect if already disposed of window */
- void FloatMenuClose(FloatMenuHandle fmenu)
- {
- require(FloatMenuValid(fmenu));
- OffscreenEnd((**fmenu).offscreen);
- (**fmenu).offscreen = NULL;
- WinUnregister((**fmenu).window, fmenu);
- WinEnd((**fmenu).window);
- (**fmenu).window = NULL;
- FloatMenuForget(fmenu);
- ensure(FloatMenuValid(fmenu));
- }
-
- /* return the ID of the floating menu */
- short FloatMenuID(FloatMenuHandle fmenu)
- {
- require(FloatMenuValid(fmenu));
- return((**(**fmenu).menu).menuID);
- }
-
- /* return the last item selected from the floating menu */
- short FloatMenuItem(FloatMenuHandle fmenu)
- {
- require(FloatMenuValid(fmenu));
- return((**fmenu).item);
- }
-
- /* return the floating menu with the specified id */
- FloatMenuHandle FloatMenuFindID(short id)
- {
- FloatMenuHandle item = NULL;
-
- for (item = gFloatMenuList; item && FloatMenuID(item) != id; item = LLHNext(item))
- ;
- ensure(! item || FloatMenuID(item) == id);
- return(item);
- }
-
- /* draw the floating menu directly into its port */
- static void FloatMenuDrawDirect(FloatMenuHandle fmenu)
- {
- Rect bounds;
- GrafPtr port = NULL;
-
- require(FloatMenuValid(fmenu));
- require((**fmenu).window != NULL);
-
- /* setup */
- GetPort(&port);
- SetPort((**fmenu).window);
-
- /* draw menu */
- WinPortRect((**fmenu).window, &bounds);
- EraseRect(&bounds);
- SetTopMenuItem(0);
- SetAtMenuBottom(bounds.bottom);
- mdef_draw((**fmenu).menu, &bounds);
-
- /* gray out items */
- if (! MenuEnabled((**fmenu).menu, 0)) {
- PenState pen;
- GetPenState(&pen);
- PenMode(patBic);
- PenPat(gray);
- PaintRect(&bounds);
- SetPenState(&pen);
- }
-
- SetPort(port);
- ensure(FloatMenuValid(fmenu));
- }
-
- /* call-back from the offscreen bitmap for drawing the menu */
- static void FloatMenuDrawHook(OffscreenHandle offscreen, void *data)
- {
- FloatMenuDrawDirect(data);
- }
-
- /* Draw the floating menu either from an offscreen bitmap or directly to
- the screen if there's no offscreen bitmap. */
- void FloatMenuDraw(FloatMenuHandle fmenu)
- {
- require(FloatMenuValid(fmenu));
- if ((**fmenu).offscreen)
- OffscreenDraw((**fmenu).offscreen);
- else
- FloatMenuDrawDirect(fmenu);
- ensure(FloatMenuValid(fmenu));
- }
-
- /* true if data describing the menu's appearance have changed since the
- last time the data were saved */
- Boolean FloatMenuChanged(FloatMenuHandle fmenu)
- {
- Boolean result = true;
- register char *p, *q;
- register size_t n;
-
- require(FloatMenuValid(fmenu));
- CalcMenuSize((**fmenu).menu);
- if (*(**fmenu).menuCopy) {
- n = HandleSize((**fmenu).menu);
- if (n == HandleSize((**fmenu).menuCopy)) {
- p = *(Handle) (**fmenu).menu;
- q = *(Handle) (**fmenu).menuCopy;
- while (n > 0 && *p++ == *q++)
- n--;
- if (n == 0)
- result = false;
- }
- }
- ensure(FloatMenuValid(fmenu));
- return(result);
- }
-
- /* save a copy of data describing the menu's appearance so it can be
- checked later */
- void FloatMenuRemember(FloatMenuHandle fmenu)
- {
- Handle copy;
- size_t n;
-
- require(FloatMenuValid(fmenu));
- n = HandleSize((**fmenu).menu);
- MemCheck(n);
- ReallocateHandle((**fmenu).menuCopy, n);
- BlockMove(*(Handle) (**fmenu).menu, *(Handle) (**fmenu).menuCopy, n);
- ensure(! FloatMenuChanged(fmenu));
- }
-
- /* forget the copy of the data describing the menu's appearance */
- void FloatMenuForget(FloatMenuHandle fmenu)
- {
- require(FloatMenuValid(fmenu));
- EmptyHandle((Handle) (**fmenu).menuCopy);
- ensure(FloatMenuChanged(fmenu));
- }
-
- /* if the hiliting, appearance, or size of the menu has changed then the
- window is adjusted and the menu is redisplayed */
- void FloatMenuAdjust(FloatMenuHandle fmenu)
- {
- Rect portRect; /* window's port rectangle */
- Rect newRect; /* rectangle for new windows */
- Point size; /* width and height of window */
-
- require(FloatMenuValid(fmenu));
- require((**fmenu).window != NULL);
-
- /* only adjust if menu's data have changed */
- if (FloatMenuChanged(fmenu)) {
-
- /* calculate size of window */
- WinNewRect((**fmenu).window, &newRect);
- WinPortRect((**fmenu).window, &portRect);
- size.h = min((**(**fmenu).menu).menuWidth, RectWidth(&newRect));
- size.v = min((**(**fmenu).menu).menuHeight, RectHeight(&newRect));
-
- /* disallow long menus since scrolling doesn't work very well */
- check((**(**fmenu).menu).menuHeight <= RectHeight(&newRect));
-
- /* adjust window size */
- if (size.h != RectWidth(&portRect) || size.v != RectHeight(&portRect)) {
- WinSize((**fmenu).window, size.h, size.v);
- WinPortRect((**fmenu).window, &portRect);
- if ((**fmenu).offscreen)
- OffscreenBoundsSet((**fmenu).offscreen, &portRect);
- }
-
- /* draw menu and remember menu's data */
- if ((**fmenu).offscreen)
- OffscreenChange((**fmenu).offscreen);
- FloatMenuDraw(fmenu);
- FloatMenuRemember(fmenu);
- }
- ensure(! FloatMenuChanged(fmenu));
- }
-
- /* Adjust display of the floating menus. Checks if the appearance of any of the
- floating menus has changed and must be redrawn. Should be called after all
- the other menu adjust handlers of the application, since those handlers
- are responsible for enabling and disabling the menu items. */
- void FloatMenuAdjustAll(void)
- {
- FloatMenuHandle fmenu = NULL;
-
- for (fmenu = gFloatMenuList; fmenu; fmenu = LLHNext(fmenu)) {
- if ((**fmenu).window)
- FloatMenuAdjust(fmenu);
- }
- }
-
- /* unhilite the most recently selected item in the menu */
- void FloatMenuUnhilite(FloatMenuHandle fmenu)
- {
- Point hit = { -1, -1 };
- Rect bounds;
- short item = 0;
- GrafPtr port = NULL;
-
- require(FloatMenuValid(fmenu));
- require((**fmenu).window);
- GetPort(&port);
- SetPort((**fmenu).window);
- WinPortRect((**fmenu).window, &bounds);
- item = (**fmenu).item;
- SetTopMenuItem(0);
- SetAtMenuBottom(bounds.bottom);
- mdef(mFloatChooseMsg, (**fmenu).menu, &bounds, hit, &item);
- SetPort(port);
- ensure(FloatMenuValid(fmenu));
- }
-
- /* track the cursor and select an item from the menu */
- void FloatMenuChoose(FloatMenuHandle fmenu)
- {
- Point hit; /* mouse location */
- Rect bounds; /* bounds of menu */
- short item = 0; /* selected item */
- long delay = 0; /* for flashing menu */
- GrafPtr port = NULL; /* saved port */
- MenuPickType pick; /* menu selection */
-
- require(FloatMenuValid(fmenu));
- require((**fmenu).window);
-
- /* adjust menus to ensure that items that are supposed to be disabled
- are really disabled, otherwise we might generate a command that the
- application can't handle */
- EventAdjustMenu();
- if (MenuEnabled((**fmenu).menu, 0)) {
-
- /* Select item from menu, using the menu definition function to hilite
- the item that the mouse is over. We pass the menu definition function
- the message 'mFloatChooseMsg' to differentiate choosing from a torn
- off menu from choosing from a menu still attached to the menu bar.
- This prevents us from trying to tear off a menu that has already
- been torn off. */
- GetPort(&port);
- SetPort((**fmenu).window);
- WinPortRect((**fmenu).window, &bounds);
- SetTopMenuItem(0);
- SetAtMenuBottom(bounds.bottom);
- do {
- GetMouse(&hit);
- mdef(mFloatChooseMsg, (**fmenu).menu, &bounds, hit, &item);
- } while (StillDown());
- (**fmenu).item = item;
- SetPort(port);
-
- if (item) {
- /* execute the selected menu item */
- Delay(8, &delay);
- FloatMenuUnhilite(fmenu);
- pick = MenuPick(FloatMenuID(fmenu), item);
- EventMenu(&pick);
- }
- }
- ensure(FloatMenuValid(fmenu));
- }
-
- /* free up memory used by floating windows */
- void FloatMenuMemoryLow(void)
- {
- FloatMenuHandle fmenu = NULL;
-
- /* Once a floating menu has been disposed of, the user can't recreate
- it by tearing off the menu since the menu's defProc field will have
- been restored. To prevent the menu from being completely disposed
- of we first dispose of less vital blocks of memory. */
- fmenu = gFloatMenuList;
- while (fmenu && MemWarning() >= MEM_WARNING_VERY_LOW) {
- if ((**fmenu).offscreen)
- OffscreenPurge((**fmenu).offscreen);
- fmenu = LLHNext(fmenu);
- }
- fmenu = gFloatMenuList;
- while (fmenu && MemWarning() >= MEM_WARNING_VERY_LOW) {
- FloatMenuClose(fmenu);
- fmenu = LLHNext(fmenu);
- }
- while (gFloatMenuList && MemWarning() >= MEM_WARNING_CRITICAL)
- FloatMenuEnd(gFloatMenuList);
- }
-